/**
 * 
 */
package com.app.framework.orm.session;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.app.framework.exception.JSONException;
import com.app.framework.exception.ORMException;
import com.app.framework.orm.StorableModel;
import com.app.framework.orm.StreamHelper;
import com.app.framework.orm.util.ClassHelper;
import com.app.framework.util.JSONUtil;
import com.app.framework.util.ReflectionUtil;
import com.app.framework.util.helper.ClassInfo;
import com.app.model.IndexEntry;
import com.app.store.Store;
import com.app.store.StoreFactory;
import com.app.util.ClassTableUtil;

/**
 * @author lisong
 * @version 2013-3-6 上午10:20:08
 */


public class PersistenceSession {
	private final Map<String,StorableModel> readEntityMap;
	private final Map<String,StorableModel> writeEntityMap;
	private final Map<String,Class<StorableModel>> removeEntityMap;
	private final StreamHelper stream = StreamHelper.getInstance();
	private final Store store;
	//当调用锁成功的时候 把key存入map 当是释放锁的时候 判断是否有key如果有则释放, 没有则跳过,目的防止释放memcache中其他线程的锁记录
	private final Map<String,String> lock = new HashMap<String,String>();
	public PersistenceSession () {
		readEntityMap = new HashMap<String,StorableModel>();
		writeEntityMap = new HashMap<String,StorableModel>();
		removeEntityMap = new HashMap<String,Class<StorableModel>>();
		store = StoreFactory.newColumnStore();
	}
	
	private Class<?> formatKey(StringBuilder cacheKey,long uid,Class<?> clz) {
		clz = ClassHelper.getRecordClass(clz);
		cacheKey.append(clz.getName()).append(":").append(uid);
		return clz;
	}

	private long parseCacheKey(String cacheKey) {
		long key = Long.parseLong(cacheKey.substring(cacheKey.indexOf(":") + 1));
		return key;
	}
	
	public void put(StorableModel record) throws ORMException {
		if(record != null) {
			
			stream.cascadePut(record,this);//级联存储引用类型
			
			if(!record.modified_get()) {
				return;
			}	
			StringBuilder cacheKey = new StringBuilder();
			formatKey(cacheKey,record.getId(), record.getClass());
			writeEntityMap.put(cacheKey.toString(), record);
		}
	}
	
	public void put(Collection<StorableModel> collection) throws ORMException {
		for(StorableModel record : collection) {
			put(record);
		}
	}
	
	public Map<String,StorableModel> getWriteEntityMap() {
		return writeEntityMap;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends StorableModel> T get(long id,Class<T> clz) throws ORMException, JSONException {
		StringBuilder buffer = new StringBuilder();
		clz = (Class<T>) formatKey(buffer, id, clz);
		String className = clz.getName();
		
		String cacheKey = buffer.toString();

		if(removeEntityMap.containsKey(cacheKey)) {
			return null;
		}
		
		StorableModel value =  readEntityMap.get(cacheKey);
		if(value == null) {
			value = writeEntityMap.get(cacheKey);
		}
		if(value == null) {
			String jsonValue = store.getEntity(ClassTableUtil.getTableName(className), id);
			value = stream.json2Object(JSONUtil.toMap(jsonValue), (Class<StorableModel>)clz,this);
			readEntityMap.put(cacheKey, value);
		}
		return (T) value;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends StorableModel> Map<Long,T> get(List<Long> keyList,Class<T> clz) throws ORMException, JSONException{
		Map<Long,T> resultMap = new HashMap<Long,T>();
		if(keyList == null || keyList.size() == 0) {
			return resultMap;
		}
		
		Map<Long,String> notInCache = new HashMap<Long,String>();
		StorableModel value = null;
		for(long uid : keyList) {
			StringBuilder tmp = new StringBuilder();
			clz = (Class<T>) formatKey(tmp,uid, clz);
			String cacheKey = tmp.toString();
			if(removeEntityMap.containsKey(cacheKey)) {
				continue;
			}
			value = readEntityMap.get(cacheKey);
			if(value == null) {
				value = writeEntityMap.get(cacheKey);
			}
			if(value == null) {
				notInCache.put(uid, cacheKey);
			}else {
				resultMap.put(uid, (T)value);
			}
		}
		if(notInCache.size()>0) {
			Map<Long,String> noInCacheMap = store.getEntityList(ClassTableUtil.getTableName(clz.getName()), new ArrayList<Long>(notInCache.keySet()));
			for(long uid : noInCacheMap.keySet()) {
				String jsonValue = noInCacheMap.get(uid);
				value = stream.json2Object(JSONUtil.toMap(jsonValue), (Class<StorableModel>)clz,this);
				readEntityMap.put(notInCache.get(uid), value);
				resultMap.put(uid, (T) value);
			}
		}
		return resultMap;
	}
	
	/**
	 * 把数据存储到数据库
	 */
	private static final int FLUSH_SIZE = 64;
	
	public void flush() throws ORMException, JSONException {
		
		while(writeEntityMap.size()>0) {
			int count = 0;
			List<String> tableNameList = new ArrayList<String>();
			List<Long> keyList = new ArrayList<Long>();
			List<String> valueList = new ArrayList<String>();
			List<List<String>> indexAttrList = new ArrayList<List<String>> ();
			List<List<String>> indexValueList = new ArrayList<List<String>> ();
			List<String> indexAttr;
			List<String> indexValue;
			Map<String, Object> value=null;
			List<LinkedHashMap<String, String>> changedList = new ArrayList<LinkedHashMap<String, String>>();
			Set<String> keySet = new HashSet<String>(writeEntityMap.keySet());
			for(String uid:keySet) {
				
				StorableModel entity = writeEntityMap.get(uid);
				String className = ClassHelper.getRecordClass(entity.getClass()).getName();
				long key = entity.getId();
				indexAttr = new ArrayList<String>();
				indexValue = new ArrayList<String>();
				LinkedHashMap<String, String> changed = new LinkedHashMap<String, String>();
				value = stream.object2json(entity,indexAttr, indexValue,changed);
				tableNameList.add(ClassTableUtil.getTableName(className));
				keyList.add(key);
				valueList.add(JSONUtil.toJSON(value));
				indexAttrList.add(indexAttr);
				indexValueList.add(indexValue);
				changedList.add(changed);
				writeEntityMap.remove(uid);
				if(writeEntityMap.size()== 0) {
					break;
				}
				if(++count>=FLUSH_SIZE) {
					break;
				}
			}
			store.putMultiEntityList(tableNameList, keyList, valueList, indexAttrList, indexValueList, changedList, false);
		}
		
		while(removeEntityMap.size() >0) {
			int count = 0;
			Set<String> keySet = new HashSet<String>(removeEntityMap.keySet());
			
			Map<String,List<Long>> classMap = new HashMap<String,List<Long>>();
			for(String key : keySet) {
				Class<StorableModel> clz = removeEntityMap.get(key);
				String className = clz.getName();
				if(!classMap.containsKey(className)) {
					List<Long> keyList = new ArrayList<Long>();
					classMap.put(className, keyList);
				}
				classMap.get(className).add(parseCacheKey(key));
				removeEntityMap.remove(key);
				if(removeEntityMap.size() == 0) {
					break;
				}
				count++;
				if(count >= FLUSH_SIZE) {
					break;
				}
			}
			for(String className : classMap.keySet()) {
				store.delEntityList(ClassTableUtil.getTableName(className), classMap.get(className), false);
			}
		}
	}
	
	@SuppressWarnings("unchecked")
	public void remove(StorableModel record) throws ORMException {
		if(record != null) {
			
			stream.cascadeRemove(record, this);
			
			long key = record.getId();
			StringBuilder sb = new StringBuilder();
			Class<?> clz = formatKey(sb, key, record.getClass());
			String cacheKey = sb.toString();
			writeEntityMap.remove(cacheKey);
			
			readEntityMap.remove(cacheKey);
			
			removeEntityMap.put(cacheKey, (Class<StorableModel>) clz);
		}
	}
	
	@SuppressWarnings("unchecked")
	public void remove(long id,Class<? extends StorableModel> clz ) throws ORMException {
		ClassInfo info = ReflectionUtil.getClassInfo(clz);
		if(info.getCascadeRemoveMethods().size() > 0) {
			throw new ORMException("the class have cascade field,must invoke the method of remove(StorableModel record)");
		}
		StringBuilder sb = new StringBuilder();
		Class<?> tmp = formatKey(sb, id, clz);
		String cacheKey = sb.toString();
		writeEntityMap.remove(cacheKey);
		readEntityMap.remove(cacheKey);
		removeEntityMap.put(cacheKey, (Class<StorableModel>) tmp);
	}
	
	public void remove(Collection<StorableModel> collection) throws ORMException {
		for(StorableModel record : collection) {
			remove(record);
		}
	}
	
	public void close() {
		//清除掉所有缓存数据
		readEntityMap.clear();
		writeEntityMap.clear();
		removeEntityMap.clear();
	}

	/**
	 * 如果key不存在，操作之前，key就会被置为0
	 * @param key
	 * @param num
	 * @return 增加之后的value值
	 * @throws ORMException
	 */
	public long increment(String key, int num) throws ORMException {
		return store.increment(key, num);
	}
	/**
	 * 如果key不存在，操作之前，key就会被置为0
	 * @param key
	 * @param num
	 * @return 增加之后的value值
	 * @throws ORMException
	 */
	public long increment(String key) throws ORMException {
		return store.increment(key);
	}
	/**
	 * 如果key不存在，操作之前，key就会被置为0
	 * @param key
	 * @param num
	 * @return 减少之后的value值
	 * @throws ORMException
	 */
	public long decrement(String key, int num) throws ORMException {
		return store.decrement(key, num);
		
	}
	/**
	 * 如果key不存在，操作之前，key就会被置为0 
	 * @param key
	 * @param num
	 * @return 减少之后的value值
	 * @throws ORMException
	 */
	public long decrement(String key) throws ORMException {
		return store.decrement(key, 1);
	}
	/**
	 * 只从memcache中获取,不同于 get方法
	 * @param key
	 * @return
	 * @throws ORMException
	 */
	public String getOnlyCache(String key) throws ORMException {
		return store.getOnlyCache(key);
	}
	
	public boolean delOnlyCache(String key) throws ORMException {
		return store.delOnlyCache(key) == 1l;
	}
	
	/**
	 * 只存储到memcache中
	 * @param key
	 * @param value
	 * @param timeout
	 * @throws ORMException
	 */
	public void putOnlyCache(String key, String value, int timeout)
			throws ORMException {
		store.putOnlyCache(key, value, timeout);
	}
	/**
	 * 锁记录
	 * @param key
	 * @return
	 * @throws ORMException
	 */
	public boolean lockRecord(String key) throws ORMException {
		
		if(lock.containsKey(key)) {
			return true;
		}
		
		boolean ret = store.lockRecord(key); 
		if(ret) {
			lock.put(key, key);
		}
		return ret;
	}
	/**
	 * 释放锁记录
	 * @param key
	 * @return
	 * @throws ORMException
	 */
	public boolean releaseRecord(String key) throws ORMException {
		
		if(!lock.containsKey(key)) {//该persistence所在的线程没有锁住key
			return true;
		}
		boolean ret = store.releaseRecord(key); 
		if(ret) {
			lock.remove(key);
		}
		return ret;
	}
	
	/**
	 * <b>获取符合某个查找条件的所有对象实体</b>
	 * 
	 * @param query
	 *            查找条件
	 * @return 返回符合条件的对象实体的列表不会为null
	 * @throws ORMException
	 * @throws JSONException
	 */
	@SuppressWarnings("unchecked")
	public List<?> query(Query query) throws ORMException, JSONException {
		List<String> jsonList = store.queryEntityList(ClassTableUtil.getTableName(query.getClassType().getName()), query.getCondition()); 
		List<StorableModel> ret = new ArrayList<StorableModel>();
		for(String json : jsonList) {
			StorableModel value = stream.json2Object(JSONUtil.toMap(json), (Class<StorableModel>) query.getClassType(),this);
			long key = value.getId();
			StringBuilder buffer = new StringBuilder();
			formatKey(buffer, key, value.getClass());
			String cacheKey = buffer.toString();
			if(removeEntityMap.containsKey(cacheKey)) {
				continue;
			}
			readEntityMap.put(cacheKey, value);
			ret.add(value);
			}
			return ret; 
	}
	/**
	 * 返回符合条件的记录的key值以及索引值
	 * @param query
	 * @return map key 主键; value 索引值
	 * @throws ORMException
	 * @throws JSONException
	 */
	public Map<Long,Object> queryIndex(Query query) throws ORMException, JSONException {
		Map<Long,Object> ret = new HashMap<Long,Object>();
		List<IndexEntry> lst = store.queryIndexList(ClassTableUtil.getTableName(query.getClassType().getName()), query.getCondition());
		for(IndexEntry ie : lst) {
			ret.put(ie.getId(), ie.getValue());
		}
		return ret;
	}
	/**
	 * 按索引计算记录数目
	 * @param clz
	 * @param indexAttrName
	 * @return
	 * @throws ORMException
	 */
	public long countEntities(Class<StorableModel> clz)
			throws ORMException {
		return store.countEntities(ClassTableUtil.getTableName(clz.getName()));
	}
	/**
	 * 按条件计算记录数目
	 * @param clz
	 * @param condition
	 * @return
	 * @throws ORMException
	 */
	public long countWithCondition(Query query)
			throws ORMException {
		return store.countWithCondition(ClassTableUtil.getTableName(query.getClassType().getName()), query.getCondition());
	}
	
	public long getMaxId(Class<StorableModel> clz) throws ORMException {
		return store.getMaxEntityId(ClassTableUtil.getTableName(clz.getName()));
	}
	
	
}
